﻿#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Globalization;
using Yarr;

namespace Yarr.YarrPad
{
    /// <summary>
    /// Interaction logic for Debugger.xaml
    /// </summary>
    public partial class DbgConsole : Window
    {
        private static double left, top;
        private static Dictionary<Symbol, Boolean> bkptSymbols = new Dictionary<Symbol, Boolean>();

        private StackFrame[] debugCallStack;
        private Yarr.Environment topEnvironment;
        private Dictionary<Type, PrintMacro> pprintDispatcher;
        private IDebugger yarrDebugger;


        public Dictionary<Type, PrintMacro> PPrintDispatcher
        {
            get { return pprintDispatcher; }
            set { pprintDispatcher = value; }
        }

        public Yarr.Environment TopEnvironment
        {
            get { return topEnvironment; }
            set { topEnvironment = value; }
        }


        public DbgConsole()
        {
            InitializeComponent();

            if (top == 0)
                top = this.Top;
            else
                this.Top = top;

            if (left == 0)
                left = this.Left;
            else
                this.Left = left;
        }


        public void InitDebugger(IDebugger debugger, StackFrame currentFrame)
        {
            this.yarrDebugger = debugger;
            debugCallStack = debugger.GetCallstack();
            this.bugConsole.console.Environment = debugCallStack[0].ExecutionEnvironment;
            this.topEnvironment = currentFrame.ExecutionEnvironment;

            yarrDebugger.SetMode(DebuggerMode.SingleStep, currentFrame, 0);
            pprintDispatcher = (Dictionary<Type, PrintMacro>)
                                this.topEnvironment["*pprint-dispatch*"];

            UpdateBreakpoints();
            UpdateCallStack();
            PrettyPrintForm(0);
        }

        private void PrettyPrintForm(int stackIndex)
        {
            if (pprintDispatcher == null)
                bugForm.Text = debugCallStack[stackIndex].Form.ToString();
            else
            {
                PrintState result = new PrintState(25, 2, pprintDispatcher);
                PrettyPrinter.PprintDispatch(debugCallStack[stackIndex].Form, result,
                                             debugCallStack[stackIndex].ExecutionEnvironment);
                bugForm.Text = result.ToString();
            }
        }

        private void UpdateBreakpoints()
        {
            breakPoints.Items.Clear();

            lock (bkptSymbols)
            {
                foreach (Symbol bkp in bkptSymbols.Keys)
                {
                    breakPoints.Items.Add(String.Format(CultureInfo.CurrentCulture, "{0}: {1}",
                                            bkptSymbols[bkp] ? " enabled" : "disabled",
                                            bkp.Name));
                }
            }

            if (breakPoints.Items.Count == 0)
            {
                this.ToggleBkptMenu.IsEnabled = false;
                this.RemoveBkptMenu.IsEnabled = false;
            }
            else
            {
                this.ToggleBkptMenu.IsEnabled = true;
                this.RemoveBkptMenu.IsEnabled = true;
            }
        }

        private void UpdateCallStack()
        {
            this.callStack.Items.Clear();
            foreach (StackFrame frame in debugCallStack)
            {
                string elt = frame.Form.ToString();
                if (elt.Length > 40)
                    elt = elt.Substring(0, 35) + " ...";

                this.callStack.Items.Add(String.Format(CultureInfo.CurrentCulture, "[{0,4}] {1}", frame.StackDepth, elt));
            }

            this.callStack.SelectedIndex = 0;
        }

        private void ToggleBreakpoint()
        {
            if (breakPoints.Items.Count > 0)
            {
                Symbol bkptName = Symbol.FromName(
                            ((string)breakPoints.Items[breakPoints.SelectedIndex]).Substring(10).Trim());
                Object bkptObj = topEnvironment[bkptName];

                bool containsKey;
                lock (bkptSymbols)
                {
                    containsKey = bkptSymbols.ContainsKey(bkptName);
                }
                if (bkptObj != null && containsKey)
                {
                    bool isActive;
                    lock (bkptSymbols)
                    {
                        bkptSymbols[bkptName] = isActive = !bkptSymbols[bkptName];
                    }

                    if (isActive)
                        yarrDebugger.EnableBreakpoint(bkptObj);
                    else
                        yarrDebugger.DisableBreakpoint(bkptObj);
                }

                UpdateBreakpoints();
            }
        }

        private void RemoveBreakpoint()
        {
            Symbol bkptName = Symbol.FromName(
                        ((string)breakPoints.Items[breakPoints.SelectedIndex]).Substring(10).Trim());
            Object bkptObj = topEnvironment[bkptName];

            bool containsKey;
            lock (bkptSymbols)
            {
                containsKey = bkptSymbols.ContainsKey(bkptName);
            }

            if (bkptObj != null && containsKey)
            {
                yarrDebugger.RemoveBreakpoint(bkptObj);

                lock (bkptSymbols)
                {
                    if (bkptSymbols.ContainsKey(bkptName))
                        bkptSymbols.Remove(bkptName);
                }

                UpdateBreakpoints();
            }
        }

        private void AddBreakpoint()
        {
            InputDlg dlg = new InputDlg("Add Breakpoint", "Breakpoint:");
            bool? dlgOk = dlg.ShowDialog();
            if (dlgOk.HasValue && dlgOk.Value)
            {
                if (!String.IsNullOrEmpty(dlg.InputText))
                {
                    Symbol bkptName = Symbol.FromName(dlg.InputText);
                    Object bkptObj = topEnvironment.GetValue(bkptName);

                    if (bkptObj != null)
                    {
                        yarrDebugger.AddBreakpoint(bkptObj);

                        lock (bkptSymbols)
                        {
                            bkptSymbols[bkptName] = true;
                        }
                    }

                    UpdateBreakpoints();
                }
            }
        }


        #region Event handlers

        private void callStack_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                this.bugConsole.console.Environment =
                    debugCallStack[callStack.SelectedIndex].ExecutionEnvironment;
            }
            finally
            {
                PrettyPrintForm(callStack.SelectedIndex);
            }
        }


        private void stepIn_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                yarrDebugger.SetMode(DebuggerMode.SingleStep,
                                    debugCallStack[callStack.SelectedIndex], -1);
            }
            finally
            {
                this.Close();
            }
        }

        private void stepOver_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                yarrDebugger.SetMode(DebuggerMode.StepOver,
                                    debugCallStack[callStack.SelectedIndex], -1);
            }
            finally
            {
                this.Close();
            }
        }

        private void stepOut_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                yarrDebugger.SetMode(DebuggerMode.RunToPreviousFrame,
                                    debugCallStack[callStack.SelectedIndex], 1);
            }
            finally
            {
                this.Close();
            }
        }

        private void debugGo_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                yarrDebugger.SetMode(DebuggerMode.RunToBreakpoint,
                                    debugCallStack[callStack.SelectedIndex], -1);
            }
            finally
            {
                this.Close();
            }
        }

        private void debugQuit_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                yarrDebugger.SetMode(DebuggerMode.RunToCompletion,
                                    debugCallStack[callStack.SelectedIndex], -1);
            }
            finally
            {
                this.Close();
            }
        }

        private void debugAbort_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                yarrDebugger.SetMode(DebuggerMode.Abort,
                                     debugCallStack[callStack.SelectedIndex], -1);
            }
            finally
            {
                this.Close();
            }
        }

        private void YarrDbg_LocationChanged(object sender, EventArgs e)
        {
            left = this.Left;
            top = this.Top;
        }

        private void breakPoints_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            ToggleBreakpoint();
        }

        private void AddBkpt_Click(object sender, RoutedEventArgs e)
        {
            AddBreakpoint();
        }

        private void ToggleBkpt_Click(object sender, RoutedEventArgs e)
        {
            ToggleBreakpoint();
        }

        private void RemoveBkpt_Click(object sender, RoutedEventArgs e)
        {
            RemoveBreakpoint();
        }

        #endregion
    }
}
